; C near-callable function to draw an antialiased line from
; (X0,Y0) to (X1,Y1), in mode 13h, the VGA's standard 320x200 256-color
; mode. Uses an antialiasing approach published by Xiaolin Wu in the July
; 1991 issue of Computer Graphics. Requires that the palette be set up so
; that there are NumLevels intensity levels of the desired drawing color,
; starting at color BaseColor (100% intensity) and followed by (NumLevels-1)
; levels of evenly decreasing intensity, with color (BaseColor+NumLevels-1)
; being 0% intensity of the desired drawing color (black). No clipping is
; performed in DrawWuLine. Handles a maximum of 256 intensity levels per
; antialiased color. This code is suitable for use at screen resolutions,
; with lines typically no more than 1K long; for longer lines, 32-bit error
; arithmetic must be used to avoid problems with fixed-point inaccuracy.
; Tested with TASM.
;
; C near-callable as:
;   void DrawWuLine(int X0, int Y0, int X1, int Y1, int BaseColor,
;      int NumLevels, unsigned int IntensityBits);

SCREEN_WIDTH_IN_BYTES equ 320	;# of bytes from the start of one scan line
; to the start of the next
SCREEN_SEGMENT	 equ   0a000h	;segment in which screen memory resides

; Parameters passed in stack frame.
parms	struc
	dw   2 dup (?)		;pushed BP and return address
X0	dw   ?			;X coordinate of line start point
Y0	dw   ?			;Y coordinate of line start point
X1	dw   ?			;X coordinate of line end point
Y1	dw   ?			;Y coordinate of line end point
BaseColor dw   ?		;color # of first color in block used for
				;antialiasing, the 100% intensity version of the
				;drawing color
NumLevels dw   ?		;size of color block, with BaseColor+NumLevels-1
				; being the 0% intensity version of the drawing color
				; (maximum NumLevels = 256)
IntensityBits dw ?		;log base 2 of NumLevels; the # of bits used to
				; describe the intensity of the drawing color.
				; 2**IntensityBits==NumLevels
				; (maximum IntensityBits = 8)
parms	ends

	.model	 small
	.code
; Screen dimension globals, used in main program to scale.
_ScreenWidthInPixels	dw	320
_ScreenHeightInPixels	dw	200

	.code
	public	 _DrawWuLine
_DrawWuLine proc near
	push	bp			;preserve caller's stack frame
	mov	bp,sp			;point to local stack frame
	push	si			;preserve C's register variables
	push	di
	push	ds			;preserve C's default data segment
	cld				;make string instructions increment their pointers

; Make sure the line runs top to bottom.
	mov	si,[bp].X0
	mov	ax,[bp].Y0
	cmp	ax,[bp].Y1		;swap endpoints if necessary to ensure that
	jna	NoSwap			; Y0 <= Y1
	xchg	[bp].Y1,ax
	mov	[bp].Y0,ax
	xchg	[bp].X1,si
	mov	[bp].X0,si
NoSwap:

; Draw the initial pixel, which is always exactly intersected by the line
; and so needs no weighting.
	mov	dx,SCREEN_SEGMENT
	mov	ds,dx			;point DS to the screen segment
	mov	dx,SCREEN_WIDTH_IN_BYTES
	mul	dx			;Y0 * SCREEN_WIDTH_IN_BYTES yields the offset
					; of the start of the row start the initial
					; pixel is on
	add	si,ax			;point DS:SI to the initial pixel
	mov	al,byte ptr [bp].BaseColor	;color with which to draw
	mov	[si],al			;draw the initial pixel

	mov	bx,1			;XDir = 1; assume DeltaX >= 0
	mov	cx,[bp].X1
	sub	cx,[bp].X0		;DeltaX; is it >= 1?
	jns	DeltaXSet		;yes, move left->right, all set
					;no, move right->left
	neg	cx			;make DeltaX positive
	neg	bx				;XDir = -1
DeltaXSet:

; Special-case horizontal, vertical, and diagonal lines, which require no
; weighting because they go right through the center of every pixel.
	mov	dx,[bp].Y1
	sub	dx,[bp].Y0		;DeltaY; is it 0?
	jnz	NotHorz			;no, not horizontal
					;yes, is horizontal, special case
	and	bx,bx			;draw from left->right?
	jns	DoHorz			;yes, all set
	std				;no, draw right->left
DoHorz:
	lea	di,[bx+si]		;point DI to next pixel to draw
	mov	ax,ds
	mov	es,ax			;point ES:DI to next pixel to draw
	mov	al,byte ptr [bp].BaseColor ;color with which to draw
					;CX = DeltaX at this point
	rep stosb			;draw the rest of the horizontal line
	cld				;restore default direction flag
	jmp	Done			;and we're done

	align	2
NotHorz:
	and	cx,cx			;is DeltaX 0?
	jnz	NotVert			;no, not a vertical line
					;yes, is vertical, special case
	mov	al,byte ptr [bp].BaseColor ;color with which to draw
VertLoop:
	add	si,SCREEN_WIDTH_IN_BYTES ;point to next pixel to draw
	mov	[si],al			;draw the next pixel
	dec	dx			;--DeltaY
	jnz	VertLoop
	jmp	Done			;and we're done

	align	2
NotVert:
	cmp	cx,dx			;DeltaX == DeltaY?
	jnz	NotDiag			;no, not diagonal
					;yes, is diagonal, special case
	mov	al,byte ptr [bp].BaseColor ;color with which to draw
DiagLoop:
	lea	si,[si+SCREEN_WIDTH_IN_BYTES+bx]
					;advance to next pixel to draw by
					; incrementing Y and adding XDir to X
	mov	[si],al			;draw the next pixel
	dec	dx			;--DeltaY
	jnz	DiagLoop
	jmp	Done			;and we're done

; Line is not horizontal, diagonal, or vertical.
	align	2
NotDiag:
; Is this an X-major or Y-major line?
	cmp	dx,cx
	jb	XMajor			;it's X-major

; It's a Y-major line. Calculate the 16-bit fixed-point fractional part of a
; pixel that X advances each time Y advances 1 pixel, truncating the result
; to avoid overrunning the endpoint along the X axis.
	xchg	dx,cx			;DX = DeltaX, CX = DeltaY
	sub	ax,ax			;make DeltaX 16.16 fixed-point value in DX:AX
	div	cx			;AX = (DeltaX << 16) / DeltaY. Won't overflow
					; because DeltaX < DeltaY
	mov	di,cx			;DI = DeltaY (loop count)
	sub	si,bx			;back up the start X by 1, as explained below
	mov	dx,-1			;initialize the line error accumulator to -1,
					; so that it will turn over immediately and
					; advance X to the start X. This is necessary
					; properly to bias error sums of 0 to mean
					; "advance next time" rather than "advance
					; this time," so that the final error sum can
					; never cause drawing to overrun the final X
					; coordinate (works in conjunction with
					; truncating ErrorAdj, to make sure X can't
					; overrun)
	mov	cx,8			;CL = # of bits by which to shift
	sub	cx,[bp].IntensityBits	; ErrorAcc to get intensity level (8
					; instead of 16 because we work only
					; with the high byte of ErrorAcc)
	mov	ch,byte ptr [bp].NumLevels ;mask used to flip all bits in an
	dec	ch			; intensity weighting, producing
					; result (1 - intensity weighting)
	mov	bp,BaseColor[bp]	;***stack frame not available***
					;***from now on		     ***
	xchg	bp,ax			;BP = ErrorAdj, AL = BaseColor,
					; AH = scratch register

; Draw all remaining pixels.
YMajorLoop:
	add	dx,bp			;calculate error for next pixel
	jnc	NoXAdvance		;not time to step in X yet
					;the error accumulator turned over,
					;so advance the X coord
	add	si,bx			;add XDir to the pixel pointer
NoXAdvance:
	add	si,SCREEN_WIDTH_IN_BYTES ;Y-major, so always advance Y

; The IntensityBits most significant bits of ErrorAcc give us the intensity
; weighting for this pixel, and the complement of the weighting for the
; paired pixel.
	mov	ah,dh			;msb of ErrorAcc
	shr	ah,cl			;Weighting = ErrorAcc >> IntensityShift;
	add	ah,al			;BaseColor + Weighting
	mov	[si],ah			;DrawPixel(X, Y, BaseColor + Weighting);
	mov	ah,dh			;msb of ErrorAcc
	shr	ah,cl			;Weighting = ErrorAcc >> IntensityShift;
	xor	ah,ch			;Weighting ^ WeightingComplementMask
	add	ah,al			;BaseColor + (Weighting ^ WeightingComplementMask)
	mov	[si+bx],ah		;DrawPixel(X+XDir, Y,
; BaseColor + (Weighting ^ WeightingComplementMask));
	dec	di			;--DeltaY
	jnz	YMajorLoop
	jmp	Done			;we're done with this line

; It's an X-major line.
	align	2
XMajor:
; Calculate the 16-bit fixed-point fractional part of a pixel that Y advances
; each time X advances 1 pixel, truncating the result to avoid overrunning
; the endpoint along the X axis.
	sub	ax,ax			;make DeltaY 16.16 fixed-point value in DX:AX
	div	cx			;AX = (DeltaY << 16) / Deltax. Won't overflow
					; because DeltaY < DeltaX
	mov	di,cx			;DI = DeltaX (loop count)
	sub	si,SCREEN_WIDTH_IN_BYTES;back up the start X by 1, as
					; explained below
	mov	dx,-1			;initialize the line error accumulator to -1,
					; so that it will turn over immediately and
					; advance Y to the start Y. This is necessary
					; properly to bias error sums of 0 to mean
					; "advance next time" rather than "advance
					; this time," so that the final error sum can
					; never cause drawing to overrun the final Y
					; coordinate (works in conjunction with
					; truncating ErrorAdj, to make sure Y can't
					; overrun)
	mov	cx,8			;CL = # of bits by which to shift
	sub	cx,[bp].IntensityBits	; ErrorAcc to get intensity level (8
					; instead of 16 because we work only
					; with the high byte of ErrorAcc)
	mov	ch,byte ptr [bp].NumLevels ;mask used to flip all bits in an
	dec	ch			; intensity weighting, producing
					; result (1 - intensity weighting)
	mov	bp,BaseColor[bp]	;***stack frame not available***
					;***from now on		     ***
	xchg	bp,ax			;BP = ErrorAdj, AL = BaseColor,
					; AH = scratch register
; Draw all remaining pixels.
XMajorLoop:
	add	dx,bp			;calculate error for next pixel
	jnc	NoYAdvance		;not time to step in Y yet
					;the error accumulator turned over,
					; so advance the Y coord
	add	si,SCREEN_WIDTH_IN_BYTES;advance Y
NoYAdvance:
	add	si,bx			;X-major, so add XDir to the pixel pointer

; The IntensityBits most significant bits of ErrorAcc give us the intensity
; weighting for this pixel, and the complement of the weighting for the
; paired pixel.
	mov	ah,dh			;msb of ErrorAcc
	shr	ah,cl			;Weighting = ErrorAcc >> IntensityShift;
	add	ah,al			;BaseColor + Weighting
	mov	[si],ah			;DrawPixel(X, Y, BaseColor + Weighting);
	mov	ah,dh			;msb of ErrorAcc
	shr	ah,cl			;Weighting = ErrorAcc >> IntensityShift;
	xor	ah,ch			;Weighting ^ WeightingComplementMask
	add	ah,al			;BaseColor + (Weighting ^ WeightingComplementMask)
	mov	[si+SCREEN_WIDTH_IN_BYTES],ah

; DrawPixel(X, Y+SCREEN_WIDTH_IN_BYTES, BaseColor + (Weighting ^ WeightingComplementMask));
	dec	di			;--DeltaX
	jnz	XMajorLoop

Done:					;we're done with this line
	pop	ds			;restore C's default data segment
	pop	di			;restore C's register variables
	pop	si
	pop	bp			;restore caller's stack frame
	ret				;done
_DrawWuLine endp
	end
